<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>骨骼动画</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      /* 隐藏body窗口区域滚动条 */
    }
  </style>
  <!--引入three.js三维引擎-->
  <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script>
  <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/examples/js/controls/OrbitControls.js"></script>
  <!-- <script src="./three.js"></script> -->
  <!-- <script src="http://www.yanhuangxueyuan.com/threejs/build/three.js"></script> -->
</head>

<body>
  <input type="range" name="slider" min="1" max="100" step="1" value="0" style="position: absolute; top: 10px;left: 10px;"/>
  <script src="static/js/main.js"></script>
  <script>
    /**
     * 创建场景对象Scene
     */
    let scene = new THREE.Scene();
    
    let geometry = new THREE.CylinderGeometry(5, 10, 120, 50, 300)
    geometry.translate(0, 60, 0)

    /**
     * 设置几何体对象Geometry的蒙皮索引skinIndices、权重skinWeights属性
     * 实现一个模拟腿部骨骼运动的效果
     */
    //遍历几何体顶点，为每一个顶点设置蒙皮索引、权重属性
    //根据y来分段，0~60一段、60~100一段、100~120一段
    for (let i = 0; i < geometry.vertices.length; i++) {
      let vertex = geometry.vertices[i]; //第i个顶点
      if (vertex.y <= 60) {
        // 设置每个顶点蒙皮索引属性  受根关节Bone1影响
        geometry.skinIndices.push(new THREE.Vector4(0, 0, 0, 0));
        // 设置每个顶点蒙皮权重属性
        // 影响该顶点关节Bone1对应权重是1-vertex.y/60
        geometry.skinWeights.push(new THREE.Vector4(1 - vertex.y / 60, 0, 0, 0));
      } else if (60 < vertex.y && vertex.y <= 60 + 40) {
        // Vector4(1, 0, 0, 0)表示对应顶点受关节Bone2影响
        geometry.skinIndices.push(new THREE.Vector4(1, 0, 0, 0));
        // 影响该顶点关节Bone2对应权重是1-(vertex.y-60)/40
        geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 60) / 40, 0, 0, 0));
      } else if (60 + 40 < vertex.y && vertex.y <= 60 + 40 + 20) {
        // Vector4(2, 0, 0, 0)表示对应顶点受关节Bone3影响
        geometry.skinIndices.push(new THREE.Vector4(2, 0, 0, 0));
        // 影响该顶点关节Bone3对应权重是1-(vertex.y-100)/20
        geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 100) / 20, 0, 0, 0));
      }
    }

    // 材质对象
    let material = new THREE.MeshPhongMaterial({
      skinning: true, //允许蒙皮动画
      // wireframe:true,
    });
    // 创建骨骼网格模型
    let SkinnedMesh = new THREE.SkinnedMesh(geometry, material);
    SkinnedMesh.position.set(50, 120, 50); //设置网格模型位置
    SkinnedMesh.rotateX(Math.PI); //旋转网格模型
    scene.add(SkinnedMesh); //网格模型添加到场景中

    /**
     * 骨骼系统
     */
    let Bone1 = new THREE.Bone(); //关节1，用来作为根关节
    let Bone2 = new THREE.Bone(); //关节2
    let Bone3 = new THREE.Bone(); //关节3
    // 设置关节父子关系   多个骨头关节构成一个树结构
    Bone1.add(Bone2);
    Bone2.add(Bone3);
    // 设置关节之间的相对位置
    //根关节Bone1默认位置是(0,0,0)
    Bone2.position.y = 60; //Bone2相对父对象Bone1位置
    Bone3.position.y = 40; //Bone3相对父对象Bone2位置

    // 所有Bone对象插入到Skeleton中，全部设置为.bones属性的元素
    let skeleton = new THREE.Skeleton([Bone1, Bone2, Bone3]); //创建骨骼系统
    //骨骼关联网格模型
    SkinnedMesh.add(Bone1); //根骨头关节添加到网格模型
    SkinnedMesh.bind(skeleton); //网格模型绑定到骨骼系统
    // console.log(SkinnedMesh);

    /**
     * 骨骼辅助显示
     */
    let skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);
    scene.add(skeletonHelper);



    // 转动关节带动骨骼网格模型出现弯曲效果  好像腿弯曲一样
    skeleton.bones[0].rotation.x = -0.5;
    skeleton.bones[1].rotation.x = 0.5;
    skeleton.bones[2].rotation.x = -1.2;

    let axisHelper = new THREE.AxesHelper(250);
    scene.add(axisHelper);
    /**
     * 光源设置
     */
    //点光源
    let point = new THREE.PointLight(0xffffff);
    point.position.set(400, 200, 300); //点光源位置
    scene.add(point); //点光源添加到场景中
    //环境光
    let ambient = new THREE.AmbientLight(0x888888);
    scene.add(ambient);
    // console.log(scene)
    // console.log(scene.children)
    /**
     * 相机设置
     */
    let width = window.innerWidth; //窗口宽度
    let height = window.innerHeight; //窗口高度
    let k = width / height; //窗口宽高比
    let s = 200; //三维场景显示范围控制系数，系数越大，显示的范围越大
    //创建相机对象
    let camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
    // camera = new THREE.PerspectiveCamera(60, k, 1, 1000)
    camera.position.set(0, 200, 600); //设置相机位置
    camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
    /**
     * 创建渲染器对象
     */
    let renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);//设置渲染区域尺寸
    renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
    document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
    
    window.onresize = function() {
        let width = window.innerWidth; //窗口宽度
        let height = window.innerHeight; //窗口高度
        renderer.setSize(width, height);//设置渲染区域尺寸
        
        k = width / height; //窗口宽高比
        //修改相机对象属性
        camera.left = -s * k
        camera.right = s * k
        camera.top = s
        camera.bottom = -s
        camera.aspect = k
        camera.updateProjectionMatrix ()
    }
    
    Game = {
		tickLength: 16, 				// 20Hz
		lastTick: performance.now(),	// 最后更新时间
		stopKey: 0,
		canvas: null,
		context: null,
	}
	// 创建一个时钟对象Clock
	let clock = new THREE.Clock()
  let t = 80, step = 0.01, n = 0
  function render(tFrame) {
      if (tFrame === undefined) {
          tFrame = performance.now()
          Game.lastTick = performance.now()
      }
      let nextTick = Game.lastTick + Game.tickLength
      let tickNum = 0
      if (tFrame > nextTick) {
          tickNum = Math.floor((tFrame - Game.lastTick) / Game.tickLength)
      }
      Game.lastTick = tFrame
      // clock.getDelta()方法获得两帧的时间间隔
      if (n < t) {
        skeleton.bones[0].rotation.x -= step 
        skeleton.bones[1].rotation.x += step 
        skeleton.bones[2].rotation.x -= -0.3 * step 
      }
      if (n >= t && n < 2*t) {
        skeleton.bones[0].rotation.x += step 
        skeleton.bones[1].rotation.x -= step 
        skeleton.bones[2].rotation.x += -0.3 * step 
      }
      n++
      if (n >= 2 * t) n = 0
      /*
      for (let num = 0; num < tickNum; ++num) {
          for (let i = 0, l = meshes.length; i < l; ++i) {
              meshes[i].position.set((i + 0.5 - l / 2) * 150, 0, 0)
              meshes[i].rotateY(3.14 / 180)
          }
      }
      */
      //执行渲染操作   指定场景、相机作为参数
      renderer.render(scene, camera);
      Game.stopKey = requestAnimationFrame(render)
  }
  function getV(distance) {
      let g = 0.5
      let t = Math.sqrt(distance * 2 / 9)
      return g * t
  }
  render()
  let controls = new THREE.OrbitControls(camera,renderer.domElement);//创建控件对象
  // controls.addEventListener('change', render);
  </script>
</body>
</html>
